// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>

#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "build/build_config.h"
#include "chrome/browser/chrome_content_browser_client.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/browser/api/storage/storage_api.h"
#include "extensions/browser/background_script_executor.h"
#include "extensions/browser/bad_message.h"
#include "extensions/browser/browsertest_util.h"
#include "extensions/browser/extension_frame_host.h"
#include "extensions/browser/extension_web_contents_observer.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/renderer_startup_helper.h"
#include "extensions/browser/script_executor.h"
#include "extensions/browser/service_worker/service_worker_host.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/mojom/frame.mojom-test-utils.h"
#include "extensions/common/mojom/service_worker_host.mojom-test-utils.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/test_extension_dir.h"
#include "ipc/ipc_security_test_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_database.mojom-forward.h"
#include "url/gurl.h"
#include "chrome/browser/extensions/api/messaging/native_messaging_test_util.h"

namespace extensions {

// ExtensionFrameHostInterceptor is a helper for:
// - Intercepting mojom::LocalFrameHost method calls (e.g. methods
//   that would normally be handled / implemented by ExtensionFrameHost).
// - Allowing the test to mutate the method arguments (e.g. to simulate a
//   compromised renderer) before passing the method call to the usual handler.
class ExtensionFrameHostInterceptor
    : mojom::LocalFrameHostInterceptorForTesting {
 public:
  // The caller needs to ensure that `frame` stays alive longer than the
  // constructed ExtensionFrameHostInterceptor.
  explicit ExtensionFrameHostInterceptor(content::RenderFrameHost* frame)
      : frame_(frame),
        extension_frame_host_(
            ExtensionWebContentsObserver::GetForWebContents(
                content::WebContents::FromRenderFrameHost(frame_))
                ->extension_frame_host_for_testing()),
        scoped_swap_impl_(extension_frame_host_->receivers_for_testing(),
                          this) {}

  ~ExtensionFrameHostInterceptor() override = default;

  using RequestMutator =
      base::RepeatingCallback<void(mojom::RequestParams& request_params)>;
  void SetRequestMutator(RequestMutator request_mutator) {
    request_mutator_ = std::move(request_mutator);
  }

  using OpenChannelToExtensionMutator =
      base::RepeatingCallback<void(mojom::ExternalConnectionInfo& info)>;
  void SetOpenChannelToExtensionMutator(
      OpenChannelToExtensionMutator open_extension_mutator) {
    open_extension_mutator_ = std::move(open_extension_mutator);
  }

 private:
  mojom::LocalFrameHost* GetForwardingInterface() override {
    return scoped_swap_impl_.old_impl();
  }

  void Request(mojom::RequestParamsPtr params,
               RequestCallback callback) override {
    // `//extensions/common/mojom/frame.mojom` specifies that `params` is
    // non-optional.
    CHECK(params);

    content::RenderFrameHost* current_target_frame =
        extension_frame_host_->receivers_for_testing().GetCurrentTargetFrame();
    if (request_mutator_ && frame_ == current_target_frame) {
      request_mutator_.Run(*params);
    }

    GetForwardingInterface()->Request(std::move(params), std::move(callback));
  }

  void OpenChannelToExtension(
      mojom::ExternalConnectionInfoPtr info,
      mojom::ChannelType channel_type,
      const std::string& channel_name,
      const PortId& port_id,
      mojo::PendingAssociatedRemote<mojom::MessagePort> port,
      mojo::PendingAssociatedReceiver<mojom::MessagePortHost> port_host)
      override {
    CHECK(info);
    content::RenderFrameHost* current_target_frame =
        extension_frame_host_->receivers_for_testing().GetCurrentTargetFrame();
    if (open_extension_mutator_ && frame_ == current_target_frame) {
      open_extension_mutator_.Run(*info);
    }

    GetForwardingInterface()->OpenChannelToExtension(
        std::move(info), channel_type, channel_name, port_id, std::move(port),
        std::move(port_host));
  }

  const raw_ptr<content::RenderFrameHost> frame_ = nullptr;
  RequestMutator request_mutator_;
  OpenChannelToExtensionMutator open_extension_mutator_;
  const raw_ptr<ExtensionFrameHost> extension_frame_host_ = nullptr;
  const mojo::test::ScopedSwapImplForTesting<mojom::LocalFrameHost>
      scoped_swap_impl_;
};

class ServiceWorkerHostInterceptorForProcessDeath
    : public mojom::ServiceWorkerHostInterceptorForTesting {
 public:
  // We use `worker_id` to have an weak handle to the `ServiceWorkerHost`
  // which will be destroyed when this object mutates the IPC to cause
  // a bad message resulting in process death.
  explicit ServiceWorkerHostInterceptorForProcessDeath(
      const WorkerId& worker_id)
      : worker_id_(worker_id) {
    auto* host = extensions::ServiceWorkerHost::GetWorkerFor(worker_id_);
    CHECK(host);
    std::ignore = host->receiver_for_testing().SwapImplForTesting(this);
  }

  mojom::ServiceWorkerHost* GetForwardingInterface() override {
    // This should be non-null if this interface is still receiving events.
    auto* host = extensions::ServiceWorkerHost::GetWorkerFor(worker_id_);
    CHECK(host);
    return host;
  }

  void OpenChannelToExtension(
      mojom::ExternalConnectionInfoPtr info,
      mojom::ChannelType channel_type,
      const std::string& channel_name,
      const PortId& port_id,
      mojo::PendingAssociatedRemote<mojom::MessagePort> port,
      mojo::PendingAssociatedReceiver<mojom::MessagePortHost> port_host)
      override {
    CHECK(info);
    if (open_extension_mutator_) {
      open_extension_mutator_.Run(*info);
    }

    GetForwardingInterface()->OpenChannelToExtension(
        std::move(info), channel_type, channel_name, port_id, std::move(port),
        std::move(port_host));
  }

  using OpenChannelToExtensionMutator =
      base::RepeatingCallback<void(mojom::ExternalConnectionInfo& info)>;
  void SetOpenChannelToExtensionMutator(
      OpenChannelToExtensionMutator open_extension_mutator) {
    open_extension_mutator_ = std::move(open_extension_mutator);
  }

 private:
  OpenChannelToExtensionMutator open_extension_mutator_;
  const WorkerId worker_id_;
};

// Waits for a kill of the given RenderProcessHost and returns the
// BadMessageReason that caused an //extensions-triggerred kill.
//
// Example usage:
//   RenderProcessHostBadIpcMessageWaiter kill_waiter(render_process_host);
//   ... test code that triggers a renderer kill ...
//   EXPECT_EQ(bad_message::EFD_BAD_MESSAGE_PROCESS, kill_waiter.Wait());
class RenderProcessHostBadIpcMessageWaiter {
 public:
  explicit RenderProcessHostBadIpcMessageWaiter(
      content::RenderProcessHost* render_process_host)
      : internal_waiter_(render_process_host,
                         "Stability.BadMessageTerminated.Extensions") {}

  // Waits until the renderer process exits.  Returns the bad message that made
  // //extensions kill the renderer.  `std::nullopt` is returned if the
  // renderer was killed outside of //extensions or exited normally.
  [[nodiscard]] std::optional<bad_message::BadMessageReason> Wait() {
    std::optional<int> internal_result = internal_waiter_.Wait();
    if (!internal_result.has_value())
      return std::nullopt;
    return static_cast<bad_message::BadMessageReason>(internal_result.value());
  }

  RenderProcessHostBadIpcMessageWaiter(
      const RenderProcessHostBadIpcMessageWaiter&) = delete;
  RenderProcessHostBadIpcMessageWaiter& operator=(
      const RenderProcessHostBadIpcMessageWaiter&) = delete;

 private:
  content::RenderProcessHostKillWaiter internal_waiter_;
};

// Test suite covering how mojo/IPC messages are verified after being received
// from a (potentially compromised) renderer process.
class ExtensionSecurityExploitBrowserTest : public ExtensionBrowserTest {
 public:
  ExtensionSecurityExploitBrowserTest() = default;

  void SetUpOnMainThread() override {
    ExtensionBrowserTest::SetUpOnMainThread();

    host_resolver()->AddRule("*", "127.0.0.1");
    content::SetupCrossSiteRedirector(embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());
  }

  content::WebContents* active_web_contents() {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

  // Asks the `extension_id` to inject `content_script` into `web_contents`.
  // Returns true if the content script execution started successfully.
  bool ExecuteProgrammaticContentScript(content::WebContents* web_contents,
                                        const ExtensionId& extension_id,
                                        const std::string& content_script) {
    DCHECK(web_contents);
    int tab_id = ExtensionTabUtil::GetTabId(web_contents);
    const char kScriptTemplate[] = R"(
        chrome.scripting.executeScript({
            target: {tabId: %d},
            injectImmediately: true,
            func: () => { %s }
        });
    )";
    std::string background_script =
        base::StringPrintf(kScriptTemplate, tab_id, content_script.c_str());
    return BackgroundScriptExecutor::ExecuteScriptAsync(
        browser()->profile(), extension_id, background_script);
  }

  // (Asynchronously) executes the given `script` in a user script world in
  // `web_contents`, associated with the given `extension_id`
  void ExecuteUserScript(content::WebContents& web_contents,
                         const ExtensionId& extension_id,
                         const std::string& script) {
    ScriptExecutor script_executor(&web_contents);
    std::vector<mojom::JSSourcePtr> sources;
    sources.push_back(mojom::JSSource::New(script, GURL()));
    script_executor.ExecuteScript(
        mojom::HostID(mojom::HostID::HostType::kExtensions, extension_id),
        mojom::CodeInjection::NewJs(mojom::JSInjection::New(
            std::move(sources), mojom::ExecutionWorld::kUserScript,
            /*world_id=*/std::nullopt,
            blink::mojom::WantResultOption::kWantResult,
            blink::mojom::UserActivationOption::kDoNotActivate,
            blink::mojom::PromiseResultOption::kAwait)),
        ScriptExecutor::SPECIFIED_FRAMES, {ExtensionApiFrameIdMap::kTopFrameId},
        ScriptExecutor::DONT_MATCH_ABOUT_BLANK,
        mojom::RunLocation::kDocumentIdle, ScriptExecutor::DEFAULT_PROCESS,
        GURL() /* webview_src */, base::DoNothing());
  }

  // Allows messaging APIs for user scripts created by the given `extension`.
  void AllowUserScriptMessaging(const Extension& extension) {
    RendererStartupHelperFactory::GetForBrowserContext(profile())
        ->SetUserScriptWorldProperties(extension, /*world_id=*/std::nullopt,
                                       /*csp=*/std::nullopt,
                                       /*enable_messaging=*/true);
  }

  const Extension& active_extension() { return *active_extension_; }
  const ExtensionId& active_extension_id() { return active_extension_->id(); }
  const Extension& spoofed_extension() { return *spoofed_extension_; }
  const ExtensionId& spoofed_extension_id() { return spoofed_extension_->id(); }

  // Installs an `active_extension` and a separate, but otherwise identical
  // `spoofed_extension` (the only difference will be the extension id).
  void InstallTestExtensions() {
    auto install_extension =
        [this](TestExtensionDir& dir,
               const char* extra_manifest_bits) -> const Extension* {
      const char kManifestTemplate[] = R"(
          {
            %s
            "name": "ScriptInjectionTrackerBrowserTest - Programmatic",
            "version": "1.0",
            "manifest_version": 3,
            "host_permissions": ["http://foo.com/*"],
            "permissions": [
                "scripting",
                "tabs",
                "nativeMessaging",
                "storage"
            ],
            "background": {"service_worker": "background_script.js"}
          } )";
      dir.WriteManifest(
          base::StringPrintf(kManifestTemplate, extra_manifest_bits));
      dir.WriteFile(FILE_PATH_LITERAL("background_script.js"), "");
      dir.WriteFile(FILE_PATH_LITERAL("page.html"), "<p>page</p>");
      return LoadExtension(dir.UnpackedPath());
    };

    // The key below corresponds to the extension ID used by
    // ScopedTestNativeMessagingHost::kExtensionId.
    const char kActiveExtensionKey[] = R"(
        "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcBHwzDvyBQ6bDppkIs9MP4ksKqCMyXQ/A52JivHZKh4YO/9vJsT3oaYhSpDCE9RPocOEQvwsHsFReW2nUEc6OLLyoCFFxIb7KkLGsmfakkut/fFdNJYh0xOTbSN8YvLWcqph09XAY2Y/f0AL7vfO1cuCqtkMt8hFrBGWxDdf9CQIDAQAB",
    )";
    active_extension_ = install_extension(active_dir_, kActiveExtensionKey);
    spoofed_extension_ = install_extension(spoofed_dir_, "");
    ASSERT_TRUE(active_extension_);
    ASSERT_TRUE(spoofed_extension_);
    ASSERT_EQ(active_extension_id(),
              ScopedTestNativeMessagingHost::kExtensionId);
    ASSERT_NE(active_extension_id(), spoofed_extension_id());
  }

 private:
  TestExtensionDir active_dir_;
  TestExtensionDir spoofed_dir_;
  raw_ptr<const Extension, DanglingUntriaged> active_extension_ = nullptr;
  raw_ptr<const Extension, DanglingUntriaged> spoofed_extension_ = nullptr;
};

// Test suite for covering ExtensionHostMsg_OpenChannelToExtension IPC.
class OpenChannelToExtensionExploitTest
    : public ExtensionSecurityExploitBrowserTest {
 public:
  OpenChannelToExtensionExploitTest() = default;

  void SetUpOnMainThread() override {
    ExtensionSecurityExploitBrowserTest::SetUpOnMainThread();

    // Navigate to an arbitrary, mostly empty test page.  Make sure that a new
    // RenderProcessHost is created to make sure it is covered by the
    // `ipc_message_waiter_`.  (A WebUI -> http navigation should swap the
    // RenderProcessHost on all platforms.)
    GURL test_page_url =
        embedded_test_server()->GetURL("foo.com", "/title1.html");
    int old_process_id =
        active_web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID();
    EXPECT_TRUE(
        ui_test_utils::NavigateToURL(browser(), GURL("chrome://version")));
    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), test_page_url));
    int new_process_id =
        active_web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID();
    EXPECT_NE(old_process_id, new_process_id);

    // Install the extensions (and potentially spawn new RenderProcessHosts)
    // only *after* the `ipc_message_waiter_` has been constructed.
    InstallTestExtensions();
  }
};

IN_PROC_BROWSER_TEST_F(OpenChannelToExtensionExploitTest,
                       FromContentScript_BadExtensionIdInMessagingSource) {
  // Trigger sending of a valid ExtensionHostMsg_OpenChannelToExtension IPC
  // from a content script of an `active_extension_id`.
  ASSERT_TRUE(ExecuteProgrammaticContentScript(
      active_web_contents(), active_extension_id(),
      "chrome.runtime.sendMessage({greeting: 'hello'}, (response) => {});"));

  content::RenderProcessHost* main_frame_process =
      active_web_contents()->GetPrimaryMainFrame()->GetProcess();
  RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame_process);
  auto interceptor = std::make_unique<ExtensionFrameHostInterceptor>(
      active_web_contents()->GetPrimaryMainFrame());
  interceptor->SetOpenChannelToExtensionMutator(
      base::BindLambdaForTesting([this](mojom::ExternalConnectionInfo& info) {
        EXPECT_EQ(MessagingEndpoint::Type::kContentScript,
                  info.source_endpoint.type);
        EXPECT_EQ(active_extension_id(), info.source_endpoint.extension_id);
        // Mutate the IPC payload.
        info.source_endpoint.extension_id = spoofed_extension_id();
      }));
  EXPECT_EQ(bad_message::EMF_INVALID_EXTENSION_ID_FOR_CONTENT_SCRIPT,
            kill_waiter.Wait());
}

IN_PROC_BROWSER_TEST_F(OpenChannelToExtensionExploitTest,
                       FromContentScript_UnexpectedNativeAppType) {
  // Trigger sending of a valid ExtensionHostMsg_OpenChannelToExtension IPC
  // from a content script of an `active_extension_id`.
  ASSERT_TRUE(ExecuteProgrammaticContentScript(
      active_web_contents(), active_extension_id(),
      "chrome.runtime.sendMessage({greeting: 'hello'}, (response) => {});"));

  content::RenderProcessHost* main_frame_process =
      active_web_contents()->GetPrimaryMainFrame()->GetProcess();
  RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame_process);
  auto interceptor = std::make_unique<ExtensionFrameHostInterceptor>(
      active_web_contents()->GetPrimaryMainFrame());
  interceptor->SetOpenChannelToExtensionMutator(
      base::BindLambdaForTesting([this](mojom::ExternalConnectionInfo& info) {
        EXPECT_EQ(MessagingEndpoint::Type::kContentScript,
                  info.source_endpoint.type);
        EXPECT_EQ(active_extension_id(), info.source_endpoint.extension_id);
        // Mutate the IPC payload.
        info.source_endpoint.type = MessagingEndpoint::Type::kNativeApp;
      }));
  EXPECT_EQ(bad_message::EMF_INVALID_CHANNEL_SOURCE_TYPE, kill_waiter.Wait());
}

IN_PROC_BROWSER_TEST_F(OpenChannelToExtensionExploitTest,
                       FromContentScript_UnexpectedExtensionType) {
  // Trigger sending of a valid ExtensionHostMsg_OpenChannelToExtension IPC
  // from a content script of an `active_extension_id`.
  ASSERT_TRUE(ExecuteProgrammaticContentScript(
      active_web_contents(), active_extension_id(),
      "chrome.runtime.sendMessage({greeting: 'hello'}, (response) => {});"));

  content::RenderProcessHost* main_frame_process =
      active_web_contents()->GetPrimaryMainFrame()->GetProcess();
  RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame_process);
  auto interceptor = std::make_unique<ExtensionFrameHostInterceptor>(
      active_web_contents()->GetPrimaryMainFrame());
  interceptor->SetOpenChannelToExtensionMutator(
      base::BindLambdaForTesting([this](mojom::ExternalConnectionInfo& info) {
        EXPECT_EQ(MessagingEndpoint::Type::kContentScript,
                  info.source_endpoint.type);
        EXPECT_EQ(active_extension_id(), info.source_endpoint.extension_id);
        // Mutate the IPC payload.
        info.source_endpoint.type = MessagingEndpoint::Type::kExtension;
      }));
  EXPECT_EQ(bad_message::EMF_INVALID_EXTENSION_ID_FOR_EXTENSION_SOURCE,
            kill_waiter.Wait());
}

IN_PROC_BROWSER_TEST_F(OpenChannelToExtensionExploitTest,
                       FromContentScript_NoExtensionIdForExtensionType) {
  // Trigger sending of a valid ExtensionHostMsg_OpenChannelToExtension IPC
  // from a content script of an `active_extension_id`.
  ASSERT_TRUE(ExecuteProgrammaticContentScript(
      active_web_contents(), active_extension_id(),
      "chrome.runtime.sendMessage({greeting: 'hello'}, (response) => {});"));

  content::RenderProcessHost* main_frame_process =
      active_web_contents()->GetPrimaryMainFrame()->GetProcess();
  RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame_process);
  auto interceptor = std::make_unique<ExtensionFrameHostInterceptor>(
      active_web_contents()->GetPrimaryMainFrame());
  interceptor->SetOpenChannelToExtensionMutator(
      base::BindLambdaForTesting([this](mojom::ExternalConnectionInfo& info) {
        EXPECT_EQ(MessagingEndpoint::Type::kContentScript,
                  info.source_endpoint.type);
        EXPECT_EQ(active_extension_id(), info.source_endpoint.extension_id);

        // Mutate the IPC payload.
        info.source_endpoint.type = MessagingEndpoint::Type::kExtension;
        info.source_endpoint.extension_id = std::nullopt;
      }));
  EXPECT_EQ(bad_message::EMF_NO_EXTENSION_ID_FOR_EXTENSION_SOURCE,
            kill_waiter.Wait());
}

IN_PROC_BROWSER_TEST_F(OpenChannelToExtensionExploitTest,
                       FromContentScript_BadSourceUrl_FromFrame) {
  if (!base::FeatureList::IsEnabled(
          extensions_features::kExtensionSourceUrlEnforcement)) {
    GTEST_SKIP() << "...BadSourceUrl... tests require "
                 << "the kExtensionSourceUrlEnforcement feature";
  }

  // Trigger sending of a valid ExtensionHostMsg_OpenChannelToExtension IPC
  // from a content script of an `active_extension_id`.
  ASSERT_TRUE(ExecuteProgrammaticContentScript(
      active_web_contents(), active_extension_id(),
      "chrome.runtime.sendMessage({greeting: 'hello'}, (response) => {});"));

  content::RenderProcessHost* main_frame_process =
      active_web_contents()->GetPrimaryMainFrame()->GetProcess();
  RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame_process);
  auto interceptor = std::make_unique<ExtensionFrameHostInterceptor>(
      active_web_contents()->GetPrimaryMainFrame());
  interceptor->SetOpenChannelToExtensionMutator(
      base::BindLambdaForTesting([this](mojom::ExternalConnectionInfo& info) {
        EXPECT_EQ(MessagingEndpoint::Type::kContentScript,
                  info.source_endpoint.type);
        EXPECT_EQ(active_extension_id(), info.source_endpoint.extension_id);

        // Mutate `source_url` in the IPC payload.
        GURL actual_url =
            active_web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL();
        ASSERT_EQ(actual_url, info.source_url);
        GURL spoofed_url =
            embedded_test_server()->GetURL("spoofed.com", "/title1.html");
        ASSERT_NE(spoofed_url.host(), actual_url.host());
        info.source_url = spoofed_url;
      }));
  EXPECT_EQ(bad_message::EMF_INVALID_SOURCE_URL, kill_waiter.Wait());
}

IN_PROC_BROWSER_TEST_F(OpenChannelToExtensionExploitTest,
                       FromServiceWorker_BadSourceUrl) {
  if (!base::FeatureList::IsEnabled(
          extensions_features::kExtensionSourceUrlEnforcement)) {
    GTEST_SKIP() << "...BadSourceUrl... tests require "
                 << "the kExtensionSourceUrlEnforcement feature";
  }

  // Navigate the test tab to an extension page.
  // TODO(crbug.com/40874764): Remove this test step - it is only here as
  // a workaround for the bug that impacts how renderer kills are detected when
  // there are no frames in a given renderer process.
  GURL test_page_url = active_extension().GetResourceURL("page.html");
  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), test_page_url));

  // Trigger sending of a valid ExtensionHostMsg_OpenChannelToExtension IPC
  // from the service worker of an `active_extension_id`.
  ASSERT_TRUE(BackgroundScriptExecutor::ExecuteScriptAsync(
      browser()->profile(), active_extension_id(),
      "chrome.runtime.sendMessage({greeting: 'hello'});"));

  std::vector<WorkerId> service_workers =
      ProcessManager::Get(browser()->profile())
          ->GetServiceWorkersForExtension(active_extension_id());
  ASSERT_EQ(1u, service_workers.size());

  content::RenderProcessHost* service_worker_process =
      content::RenderProcessHost::FromID(service_workers[0].render_process_id);
  ASSERT_TRUE(service_worker_process);
  RenderProcessHostBadIpcMessageWaiter kill_waiter(service_worker_process);
  auto interceptor =
      std::make_unique<ServiceWorkerHostInterceptorForProcessDeath>(
          service_workers[0]);
  interceptor->SetOpenChannelToExtensionMutator(
      base::BindLambdaForTesting([this](mojom::ExternalConnectionInfo& info) {
        EXPECT_EQ(MessagingEndpoint::Type::kExtension,
                  info.source_endpoint.type);
        EXPECT_EQ(active_extension_id(), info.source_endpoint.extension_id);
        EXPECT_EQ(info.source_url.host(), active_extension_id());

        // Mutate `source_url` in the IPC payload.
        info.source_url =
            spoofed_extension().GetResourceURL("some_resource.html");
        EXPECT_EQ(info.source_url.host(), spoofed_extension_id());
      }));

  EXPECT_EQ(bad_message::EMF_INVALID_SOURCE_URL, kill_waiter.Wait());
}

// This is a regression test for https://crbug.com/1379558.
IN_PROC_BROWSER_TEST_F(ExtensionSecurityExploitBrowserTest,
                       SendMessageFromContentScriptInDataUrlFrame) {
  // Install a test extension that 1) declaratively injects content scripts into
  // all frames (including data: frames thanks to `match_origin_as_fallback`),
  // 2) sends a message from the content script to the extension, 3) echoes back
  // the `source_url` of the message sender via `chrome.test.sendMessage`.
  //
  // Note that `chrome.scripting.executeScript` is unable to inject content
  // scripts into data: frames (without additional permissions), because this
  // API doesn't have a knob equivalent to `match_origin_as_fallback`.  This is
  // the primary reason for using manifest-declared content scripts in this
  // test.
  TestExtensionDir dir;
  const char kManifestTemplate[] = R"(
      {
        "name": "source_url echo-er",
        "version": "1.0",
        "manifest_version": 3,
        "content_scripts": [{
          "all_frames": true,
          "match_about_blank": true,
          "match_origin_as_fallback": true,
          "matches": ["*://foo.com/*"],
          "js": ["content_script.js"]
        }],
        "background": {"service_worker": "background_script.js"}
      } )";
  dir.WriteManifest(kManifestTemplate);
  const char kBackgroundScript[] = R"(
      chrome.runtime.onMessage.addListener(
        function(request, sender, sendResponse) {
          chrome.test.sendMessage(sender.url);
        }
      );
  )";
  dir.WriteFile(FILE_PATH_LITERAL("background_script.js"), kBackgroundScript);
  const char kContentScript[] = R"(
      chrome.runtime.sendMessage({greeting: 'hello'}, (response) => {});
  )";
  dir.WriteFile(FILE_PATH_LITERAL("content_script.js"), kContentScript);
  const Extension* extension = LoadExtension(dir.UnpackedPath());
  ASSERT_TRUE(extension);

  // Navigate to foo.com (covered by `content_scripts.matches` above).
  GURL http_url = embedded_test_server()->GetURL("foo.com", "/title1.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), http_url));

  // Add a data: URL subframe and wait for its `source_url` to be echoed back.
  // The data: URL encodes `<p>foo</p>` HTML doc.
  const GURL kDataUrl("data:text/html;charset=utf-8;base64,PHA+Zm9vPC9wPg==");
  ExtensionTestMessageListener listener(kDataUrl.spec());
  const char kScriptTemplate[] = R"(
      new Promise(function (resolve, reject) {
          var iframe = document.createElement('iframe');
          iframe.src = $1;
          iframe.onload = () => {
              resolve("onload");
          };
          document.body.appendChild(iframe);
      });
  )";
  ASSERT_EQ("onload",
            content::EvalJs(active_web_contents(),
                            content::JsReplace(kScriptTemplate, kDataUrl)));
  ASSERT_TRUE(listener.WaitUntilSatisfied());

  // The main verification here (against https://crbug.com/1379558) is that the
  // renderer process wasn't terminated (because of incorrectly classifying IPC
  // payload as spoofed / illegitimately claiming to come on behalf of the data
  // URL).  This verification happens at the test suite level (e.g. see
  // `content::NoRendererCrashesAssertion` for more details).
}

IN_PROC_BROWSER_TEST_F(ExtensionSecurityExploitBrowserTest,
                       SpoofedExtensionId_ExtensionFunctionDispatcher) {
  InstallTestExtensions();

  // Navigate to a test page.
  GURL test_page_url =
      embedded_test_server()->GetURL("foo.com", "/title1.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_page_url));
  content::RenderFrameHost* main_frame =
      active_web_contents()->GetPrimaryMainFrame();

  // Verify the test setup by checking if the non-intercepted `chrome.storage`
  // API call will succeed.
  {
    ExtensionTestMessageListener listener("Got chrome.storage response");
    ASSERT_TRUE(ExecuteProgrammaticContentScript(active_web_contents(),
                                                 active_extension_id(), R"(
          chrome.storage.local.set(
              { test_key: 'test value'},
              () => {
                  chrome.test.sendMessage('Got chrome.storage response');
              }
          ); )"));
    ASSERT_TRUE(listener.WaitUntilSatisfied());
  }

  // Prepare to mutate the extension id in the IPC associated with the
  // `chrome.storage.local.set`.
  auto interceptor =
      std::make_unique<ExtensionFrameHostInterceptor>(main_frame);
  interceptor->SetRequestMutator(
      base::BindLambdaForTesting([this](mojom::RequestParams& request_params) {
        if (request_params.name != "storage.set")
          return;

        EXPECT_EQ(active_extension_id(), request_params.extension_id);
        request_params.extension_id = spoofed_extension_id();
      }));

  // Trigger an IPC associated with the `chrome.storage.local.set` API and
  // verify that the mutated/spoofed extension id is detected and leads to
  // terminating the misbehaving renderer process.
  RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame->GetProcess());
  ASSERT_TRUE(ExecuteProgrammaticContentScript(active_web_contents(),
                                               active_extension_id(),
                                               R"(
          chrome.storage.local.set({ test_key: 'test value2'}, () => {}); )"));
  EXPECT_EQ(bad_message::EFD_INVALID_EXTENSION_ID_FOR_PROCESS,
            kill_waiter.Wait());
}

IN_PROC_BROWSER_TEST_F(OpenChannelToExtensionExploitTest,
                       FromUserScript_BadExtensionIdInMessagingSource) {
  AllowUserScriptMessaging(active_extension());

  // Trigger sending of a valid ExtensionHostMsg_OpenChannelToExtension IPC
  // from a user script of an `active_extension_id`.
  ExecuteUserScript(
      *active_web_contents(), active_extension_id(),
      "chrome.runtime.sendMessage({greeting: 'hello'}, (response) => {});");

  content::RenderProcessHost* main_frame_process =
      active_web_contents()->GetPrimaryMainFrame()->GetProcess();
  RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame_process);
  auto interceptor = std::make_unique<ExtensionFrameHostInterceptor>(
      active_web_contents()->GetPrimaryMainFrame());
  interceptor->SetOpenChannelToExtensionMutator(
      base::BindLambdaForTesting([this](mojom::ExternalConnectionInfo& info) {
        EXPECT_EQ(MessagingEndpoint::Type::kUserScript,
                  info.source_endpoint.type);
        EXPECT_EQ(active_extension_id(), info.source_endpoint.extension_id);
        // Mutate the IPC payload.
        info.source_endpoint.extension_id.reset();
      }));
  EXPECT_EQ(bad_message::EMF_INVALID_EXTENSION_ID_FOR_USER_SCRIPT,
            kill_waiter.Wait());
}

IN_PROC_BROWSER_TEST_F(OpenChannelToExtensionExploitTest,
                       FromUserScript_SpoofedExtensionIdInMessagingSource) {
  AllowUserScriptMessaging(active_extension());

  // Trigger sending of a valid ExtensionHostMsg_OpenChannelToExtension IPC
  // from a user script of an `active_extension_id`.
  ExecuteUserScript(
      *active_web_contents(), active_extension_id(),
      "chrome.runtime.sendMessage({greeting: 'hello'}, (response) => {});");

  content::RenderProcessHost* main_frame_process =
      active_web_contents()->GetPrimaryMainFrame()->GetProcess();
  RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame_process);
  auto interceptor = std::make_unique<ExtensionFrameHostInterceptor>(
      active_web_contents()->GetPrimaryMainFrame());
  interceptor->SetOpenChannelToExtensionMutator(
      base::BindLambdaForTesting([this](mojom::ExternalConnectionInfo& info) {
        EXPECT_EQ(MessagingEndpoint::Type::kUserScript,
                  info.source_endpoint.type);
        EXPECT_EQ(active_extension_id(), info.source_endpoint.extension_id);
        // Mutate the IPC payload.
        info.source_endpoint.extension_id = spoofed_extension_id();
      }));
  EXPECT_EQ(bad_message::EMF_INVALID_EXTENSION_ID_FOR_USER_SCRIPT,
            kill_waiter.Wait());
}

IN_PROC_BROWSER_TEST_F(OpenChannelToExtensionExploitTest,
                       FromUserScript_TargetingAnotherExtensionId) {
  AllowUserScriptMessaging(active_extension());

  // Trigger sending of a valid ExtensionHostMsg_OpenChannelToExtension IPC
  // from a user script of an `active_extension_id`.
  ExecuteUserScript(
      *active_web_contents(), active_extension_id(),
      "chrome.runtime.sendMessage({greeting: 'hello'}, (response) => {});");

  content::RenderProcessHost* main_frame_process =
      active_web_contents()->GetPrimaryMainFrame()->GetProcess();
  RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame_process);
  auto interceptor = std::make_unique<ExtensionFrameHostInterceptor>(
      active_web_contents()->GetPrimaryMainFrame());
  interceptor->SetOpenChannelToExtensionMutator(
      base::BindLambdaForTesting([this](mojom::ExternalConnectionInfo& info) {
        EXPECT_EQ(MessagingEndpoint::Type::kUserScript,
                  info.source_endpoint.type);
        EXPECT_EQ(active_extension_id(), info.source_endpoint.extension_id);
        // Mutate the IPC payload.
        info.target_id = spoofed_extension_id();
      }));
  EXPECT_EQ(bad_message::EMF_INVALID_EXTERNAL_EXTENSION_ID_FOR_USER_SCRIPT,
            kill_waiter.Wait());
}

}  // namespace extensions
